package cn.kinoko.common.aspect;

import cn.kinoko.common.constant.HeaderConstant;
import cn.kinoko.common.utils.ExceptionUtil;
import cn.kinoko.common.utils.SlidingWindowRateLimiter;
import cn.kinoko.common.utils.SpelUtil;
import cn.kinoko.common.utils.ThreadLocalUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import java.time.Duration;

/**
 * 分布式缓存切面
 *
 * @author kk
 */
@Aspect
@Slf4j
@Component
public class SlidingWindowLimiterAspect implements InitializingBean {

    private Cache<String, SlidingWindowRateLimiter> limiterCache;

    @Override
    public void afterPropertiesSet() {
        this.limiterCache = Caffeine.newBuilder()
                // 5分钟未使用则过期
                .expireAfterAccess(Duration.ofSeconds(5))
                .build();
    }


    @Around("@annotation(limiter)")
    public Object around(ProceedingJoinPoint joinPoint, cn.kinoko.common.aspect.annotation.SlidingWindowLimiter limiter) throws Throwable {
        // 获取redisKey
        String redisKey = SpelUtil.getRedisKey(joinPoint, limiter.key(), limiter.params());
        if (limiter.currentUser()) {
            redisKey += (":" + ThreadLocalUtil.get(HeaderConstant.UID_HEADER));
        }
        // 获取限流器，没有则构建，并且保证只有一个线程参与构建
        SlidingWindowRateLimiter slidingWindowRateLimiter = limiterCache.get(redisKey, k -> new SlidingWindowRateLimiter(limiter.maxRequest(), limiter.windowSizeInMs(), limiter.waitTime()));
        // 尝试获取令牌
        ExceptionUtil.assertion(!slidingWindowRateLimiter.allowRequest(limiter.waitTime() > 0), limiter.errorDesc());
        // 执行业务方法
        return joinPoint.proceed();
    }

}
